import { Browser, Page, ElementHandle } from 'puppeteer';
import Site from '../common/site';
import { logger } from '../utils/Logger';
import { scrapeSleep, shuffleArray } from '../utils/index';
import prisma from '../db/prisma';
import { monitorHumanScroll } from '../utils/monitor';
import { error, log } from 'console';

// 类型定义
type StringElementHandleTuple = [string, ElementHandle];
interface GoodsData {
  taskId: string;
  source: string;
  sku_id: string;
  sku_name: string;
  final_price: string;
  jd_price: string;
  shop_name: string;
  detail: string;
}

// 淘宝特有的选择器配置
const SELECTORS = {
  nextPage:
    'button.next-btn.next-small.next-btn-normal.next-pagination-item.next-next',
  shop: 'a.doubleCardWrapperAdapt--mEcC7olq',
  goodsList: 'div.valueItem--GzWd2LsV',
  goodsPrice: 'div.displayPrice--oD54EJg0 span.text--fZ9NUhyQ',
  shopName: 'span.shopName--mTDZGIPO',
  goodsName: 'valueItem--GzWd2LsV isSelected--YrA6x4Yj',
} as const;

export default class TMAILL extends Site {
  private cookies: any = null;

  constructor(url: string) {
    super(url);
  }

  // 提取 SKU ID
  private extractSkuId(url: string): string | null {
    try {
      const parsedUrl = new URL(url);
      return parsedUrl.searchParams.get('skuId');
    } catch (err) {
      logger.error(`解析 URL 失败: ${url}, 错误: ${err}`);
      return null;
    }
  }

  // 检查是否存在下一页（带重试）
  private async hasNextPage(
    page: Page,
    selector: string,
    retries = 3,
  ): Promise<boolean> {
    for (let attempt = 0; attempt < retries; attempt++) {
      try {
        const nextButton = await page.$(selector);
        if (!nextButton) return false;

        const isDisabled = await page.$eval(selector, (el) =>
          el.hasAttribute('disabled'),
        );
        return !isDisabled;
      } catch (err) {
        logger.error(`检查下一页失败，第 ${attempt + 1} 次重试: ${err}`);
        await page.reload();
        await scrapeSleep(20000, 30000);

        if (attempt === retries - 1) {
          logger.error(`重试 ${retries} 次后失败`);
          return false;
        }
      }
    }
    return false;
  }

  // 点击进入下一页（带重试）
  private async goToNextPage(
    page: Page,
    selector: string,
    retries = 3,
  ): Promise<boolean> {
    try {
      await Promise.all([
        page.click(selector, { button: 'left', delay: 100 }),
        page.waitForNavigation({ waitUntil: 'networkidle2' }),
      ]);
      await this.autoSlideVerify(page);
      logger.debug('成功进入下一页');
      return true;
    } catch (err) {
      logger.error(`点击下一页失败: ${err}`);
      if (retries <= 1) {
        logger.error('重试次数已达上限');
        return false;
      }

      logger.debug(`等待 10-20 秒后重试...`);
      await page.reload();
      await scrapeSleep(10000, 20000);
      return this.goToNextPage(page, selector, retries - 1);
    }
  }

  // 打开搜索页面并输入关键词
  private async search(page: Page, keywords: string): Promise<Page> {
    try {
      await page.goto('https://search.taobao.com', { waitUntil: 'load' });
      await page.type('input.search-combobox-input', keywords);
      await scrapeSleep(5000, 8000);
      await page.click('button.btn-search');
      await page.waitForNavigation({ waitUntil: 'networkidle2' });
      await this.autoSlideVerify(page);
      await scrapeSleep(5000,10000);
      return page;
    } catch (err) {
      logger.error(`打开搜索页面失败: ${err}`);
      throw err;
    }
  }

  // 主爬取逻辑
  public async scrapeTMAILL(task: any, keywords: string): Promise<void> {
    const browser = await this.launchBrowser();
    let page = await browser.newPage();

    try {
      page = await this.monitorHumanOpenTab(page, {
        cookieFilePath: './src/taobao/search.taobao.com.cookies.json',
        referer: 'https://www.taobao.com/',
      });

      const searchPage = await this.search(page, keywords);
      await monitorHumanScroll(searchPage);
      await scrapeSleep();

      await this.scrapeSearchResultPage(browser, searchPage, keywords, task);
    } catch (err) {
      logger.error(`爬取失败: ${err}`);
    } finally {
      await browser.close();
    }
  }

  // 爬取搜索结果页
  private async scrapeSearchResultPage(
    browser: Browser,
    page: Page,
    keywords: string,
    task: any,
  ): Promise<void> {
    let pageCount = 1;

    while (true) {
      await scrapeSleep(5000, 60000);
      const shops = await this.getAllShops(page, SELECTORS.shop);
      const shuffledShops = shuffleArray<StringElementHandleTuple>(shops);

      if (shuffledShops.length === 0) {
        logger.warn('未找到任何店铺元素');
        break;
      }

      const originalUrl = page.url();
      for (const [skuId, shopElement] of shuffledShops) {
        if (!skuId) continue; // 跳过无 SKU 的店铺

        await scrapeSleep(5000, 20000);
        await this.visitShop(
          browser,
          page,
          shopElement,
          originalUrl,
          skuId,
          keywords,
          task,
        );
        logger.debug(`=================>${skuId}`);
      }

      pageCount++;
      if (pageCount > 5) break;

      const hasNext = await this.hasNextPage(page, SELECTORS.nextPage, 3);
      if (!hasNext) {
        logger.info('已到达最后一页');
        break;
      }
      // //进入下一页先等待20-30分钟
      // await scrapeSleep(1200000, 1800000);
      const success = await this.goToNextPage(page, SELECTORS.nextPage, 5);
      if (!success) {
        logger.error('进入下一页失败');
        break;
      } else {
        logger.info('进入下一页成功。');
      }
    }
    logger.info('网页爬取完成');
  }

  // 获取所有店铺
  private async getAllShops(
    page: Page,
    selector: string,
  ): Promise<StringElementHandleTuple[]> {
    const shops: StringElementHandleTuple[] = [];
    const elements = await page.$$(selector);

    for (const element of elements) {
      const href = await page.evaluate(
        (el) => el.getAttribute('href'),
        element,
      );
      if (href && href.trim()) {
        const fullHref = href.startsWith('http') ? href : `https:${href}`;
        const skuId = this.extractSkuId(fullHref);
        if (skuId) shops.push([skuId, element]); // 只添加有 SKU 的店铺
      }
    }
    return shops;
  }

  // 访问单个店铺
  private async visitShop(
    browser: Browser,
    page: Page,
    shopElement: ElementHandle,
    originalUrl: string,
    skuId: string,
    keywords: string,
    task: any,
  ): Promise<void> {
    
    await this.scrollToElement(page, shopElement);
    await scrapeSleep(500, 2000);
    const newPage = await this.openNewTab(browser, shopElement);
    if (!newPage) {
      logger.warn('无法打开新标签页');
      return;
    }
    const shopUrl = newPage.url();
    try {
      await this.autoSlideVerify(page);
      await scrapeSleep(5000,10000);
      await newPage.bringToFront();

      logger.debug(`访问店铺，新标签页 URL: ${shopUrl}`);

      await newPage.waitForNetworkIdle({ timeout: 10000 });
      const goodsElements = await this.getFilteredGoodsElements(
        newPage,
        keywords,
      );
      await this.clickAndScrapeGoodsDetails(
        newPage,
        goodsElements,
        task,
        skuId,
        shopUrl,
      );
    } catch (err) {
      logger.error(`处理店铺 ${shopUrl} 失败: ${err}`);
    } finally {
      await newPage.close();
      logger.debug('新标签页已关闭');
    }

    await scrapeSleep(1000, 3000);
    await page.bringToFront();
    if (page.url() !== originalUrl) {
      await page.goto(originalUrl, { waitUntil: 'networkidle2' });
    }
    logger.debug(`返回原始页面: ${page.url()}`);
  }

  private async openNewTab(
    browser: Browser,
    page: Page, // 新增 page 参数
    element: ElementHandle,
  ): Promise<Page | null> {
    try {
      // 获取元素位置和大小
      const boundingBox = await element.boundingBox();
  
      // 检查 boundingBox 是否为 null
      if (!boundingBox) {
        logger.error('无法获取元素位置，可能元素不可见或已脱离文档');
        return null;
      }
  
      // 现在 boundingBox 是 BoundingBox 类型，可以安全解构
      const { x, y, width, height } = boundingBox;
  
      // 模拟鼠标移动到元素上，带随机偏移
      const targetX = x + width * (0.3 + Math.random() * 0.4); // 偏移到30%-70%宽度
      const targetY = y + height * (0.3 + Math.random() * 0.4); // 偏移到30%-70%高度
      await page.mouse.move(targetX, targetY, { steps: 10 }); // 使用传入的 page
  
      // 随机延迟，模拟人类反应时间
      await scrapeSleep(100, 300);
  
      // 模拟中键点击打开新标签
      const [newPage] = await Promise.all([
        new Promise<Page | null>((resolve) =>
          browser.once('targetcreated', (target) => resolve(target.page())),
        ),
        element.click({ button: 'middle', delay: 50 + Math.random() * 100 }), // 点击延迟50-150ms
      ]);
  
      if (!newPage) {
        logger.error('新标签页未创建');
        return null;
      }
  
      // 等待新页面加载，模拟人类切换标签后的短暂停留
      await newPage.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {
        logger.warn('新页面加载超时，继续执行');
      });
      await scrapeSleep(200, 600);
  
      return newPage;
    } catch (err) {
      logger.error(`打开新标签页失败: ${err}`);
      return null;
    }
  }

  // 获取过滤后的商品元素
  private async getFilteredGoodsElements(
    page: Page,
    keywords: string,
  ): Promise<ElementHandle[]> {
    try {
      await page.waitForSelector(SELECTORS.goodsList, { timeout: 10000 });
      const elements = await page.$$(SELECTORS.goodsList);

      const filteredElements: ElementHandle[] = [];
      for (const element of elements) {
        const isDisabled = await element.evaluate((el: Element) =>
          el.getAttribute('class')?.includes('isDisabled'),
        );
        if (isDisabled) continue;

        const text = await element.evaluate((el) => el.textContent || '');
        if (text.includes(keywords)) {
          logger.debug(`匹配商品: ${text}`);
          filteredElements.push(element);
        }
      }
      return filteredElements;
    } catch (err) {
      logger.error(`获取商品元素失败: ${err}`);
      return [];
    }
  }

  // 点击并抓取商品详情
  private async clickAndScrapeGoodsDetails(
    page: Page,
    goodsElements: ElementHandle[],
    task: any,
    skuId: string,
    shopUrl: string,
  ): Promise<void> {
    logger.debug(`===店铺有${goodsElements.length}个商品满足条件===`);
    for (const element of goodsElements) {
      try {
        await this.scollRandomDistance(page); //随机滑动1000-2000像素距离
        await this.scrollToElement(page, element);
        await scrapeSleep(2000, 6000);

        const goods = await this.processGoods(
          page,
          element,
          task,
          skuId,
          shopUrl,
        );
        await this.insertGoods(goods);
      } catch (err) {
        logger.debug(`跳过商品: ${err}`);
      }
    }
  }

  // 处理单个商品
  private async processGoods(
    page: Page,
    element: ElementHandle,
    task: any,
    skuId: string,
    url: string,
  ): Promise<GoodsData> {
    await element.click({ delay: 1500 });
    await scrapeSleep(1000, 5000);

    const { goods_name, goods_price, shop_name } = await this.fetchGoodsDetails(
      page,
      element,
    );
    const goods: GoodsData = {
      taskId: task.id,
      source: 'taobao',
      sku_id: skuId,
      sku_name: goods_name,
      final_price: goods_price,
      jd_price: goods_price,
      shop_name,
      detail: url,
    };

    logger.info(`入库一条商品数据: ${goods_name}}: ${goods_price}`);
    return goods;
  }

  // 获取商品详情
  private async fetchGoodsDetails(
    page: Page,
    element: ElementHandle,
  ): Promise<{
    goods_name: string;
    goods_price: string;
    shop_name: string;
  }> {
    const goodsName = await element.evaluate(
      (el) => el?.textContent || '',
      await element.$(SELECTORS.goodsName),
    );
    const goodsPrice = await page.$eval(
      SELECTORS.goodsPrice,
      (el) => el.textContent || '',
    );
    const shopName = await page.$eval(
      SELECTORS.shopName,
      (el) => el.textContent || '',
    );

    return {
      goods_name: goodsName,
      goods_price: goodsPrice,
      shop_name: shopName,
    };
  }

  private async insertGoods(goods: GoodsData): Promise<void> {
    try {
      const { taskId, ...rest } = goods; // 分离 taskId
      await prisma.goods.create({
        data: {
          ...rest, // 只包含非关系字段
          task: {
            connect: { id: taskId }, // 单独处理关系
          },
        },
      });
    } catch (err) {
      logger.error(`插入商品失败: ${err}`);
      throw err;
    }
  }

  //滑动随机距离
  private async scollRandomDistance(
    page: Page,
  ): Promise<void> {
    try {
      // 记录初始滚动位置
      const initialScroll = await page.evaluate(() => window.scrollY);
  
      // 随机生成目标滑动距离（1000-2000像素）
      const targetDistance = Math.floor(Math.random() * 1001) + 1000; // [1000, 2000]
      let currentDistance = 0;
  
      // 分步滑动，每次步长随机，模拟人类行为
      const stepSizeBase = 200; // 基础步长
      while (currentDistance < targetDistance) {
        const remaining = targetDistance - currentDistance;
        const stepSize = Math.min(
          stepSizeBase * (0.5 + Math.random()), // 随机波动步长，范围[100, 300]
          remaining, // 不超过剩余距离
        );
  
        // 执行滑动
        await page.evaluate((step) => window.scrollBy(0, step), stepSize);
        currentDistance += stepSize;
  
        // 随机停顿，40%概率暂停1-3秒
        if (Math.random() < 0.4) {
          const pauseTime = Math.floor(Math.random() * 2001) + 1000; // [1000, 3000]ms
          await scrapeSleep(pauseTime, pauseTime);
        }
      }
  
      // 停顿片刻，模拟浏览结束
      await scrapeSleep(500, 1500);
  
      // 平滑滑回初始位置
      await page.evaluate(
        (initial) => window.scrollTo({ top: initial, behavior: "smooth" }),
        initialScroll,
      );
  
      // 随机小调整，增加真实感
      if (Math.random() < 0.3) {
        const adjust = (Math.random() - 0.5) * 100; // ±50像素微调
        await page.evaluate((adj) => window.scrollBy(0, adj), adjust);
        await scrapeSleep(200, 600);
        await page.evaluate(
          (initial) => window.scrollTo({ top: initial, behavior: "smooth" }),
          initialScroll,
        );
      }
    } catch (err) {
      logger.error(`模拟滑动失败: ${err}`);
      throw err;
    }
  }

  private async scrollToElement(
    page: Page,
    element: ElementHandle,
  ): Promise<void> {
    try {
      // 获取元素位置和视口信息
      const { top, height } = await page.evaluate((el) => {
        const rect = el.getBoundingClientRect();
        return { top: rect.top, height: rect.height };
      }, element);
  
      const viewportHeight = await page.evaluate(() => window.innerHeight);
      const currentScroll = await page.evaluate(() => window.scrollY);
      const targetScroll = currentScroll + top - viewportHeight / 2 + height / 2;
  
      let distance = targetScroll - currentScroll;
      const stepSizeBase = Math.abs(distance) > 500 ? 200 : 50;
  
      // 模拟非线性滑动：初始步长大，接近目标时步长减小
      while (Math.abs(distance) > stepSizeBase / 2) {
        const progress = Math.abs(distance) / Math.abs(targetScroll - currentScroll);
        const stepSize = Math.max(stepSizeBase * progress, 20); // 动态步长，最小20
        const scrollStep = distance > 0 ? stepSize : -stepSize;
  
        await page.evaluate((step) => window.scrollBy(0, step), scrollStep);
        distance = targetScroll - (await page.evaluate(() => window.scrollY));
  
        // 随机停顿，模拟扫视
        if (Math.random() < 0.4) {
          await scrapeSleep(150, 400);
        }
      }
  
      // 最后平滑滚动到目标，但允许轻微“过头”
      const overshoot = Math.random() < 0.5 ? (Math.random() - 0.5) * 100 : 0; // ±50像素随机过头
      await page.evaluate(
        (target) => window.scrollTo({ top: target, behavior: "smooth" }),
        targetScroll + overshoot,
      );
      await scrapeSleep(300, 800);
  
      // 如果过头了，模拟人类回退调整
      if (overshoot !== 0) {
        await page.evaluate(
          (target) => window.scrollTo({ top: target, behavior: "smooth" }),
          targetScroll,
        );
        await scrapeSleep(200, 500);
      }
  
      // 额外随机微调，模仿人类不确定性
      if (Math.random() < 0.3) {
        const adjust = (Math.random() - 0.5) * 80; // ±40像素微调
        await page.evaluate((adj) => window.scrollBy(0, adj), adjust);
        await scrapeSleep(200, 600);
      }
    } catch (err) {
      logger.error(`滚动到元素失败: ${err}`);
      throw err;
    }
  }


  async autoSlideVerify(page: Page) {
    // 监听页面导航
    page.on('framenavigated', frame => {
      console.log('Navigated to:', frame.url());
    });
    // 动态查找包含验证码的 frame
    let targetFrame = null;
    for (const frame of page.frames()) {
      const slider = await frame.$('#nc_1__scale_text');
      if (slider) {
        targetFrame = frame;
        break;
      }
    }

    if (!targetFrame) {
      logger.info("未找到验证码所在的 frame, 该页面无验证码");
      return;
    }

    // 等待滑块元素可见
    await targetFrame.waitForSelector('#nc_1__scale_text', { visible: true, timeout: 10000 });
    const sliderElement = await targetFrame.$('#nc_1__scale_text');
    if (!sliderElement) {
      logger.warn("没有找到滑框轨道, 验证码加载失败");
      throw error;
    }

    const sliderHandle = await targetFrame.$('span.nc_iconfont.btn_slide');
    if (!sliderHandle) {
      console.warn("没有找到滑块手柄, 验证码加载失败");
      throw error;
    }

    // 获取滑块轨道和手柄的边界框
    const sliderBox = await sliderElement.boundingBox();
    const handleBox = await sliderHandle.boundingBox();

    if (sliderBox && handleBox) {
      // 计算滑动距离：轨道宽度 - 手柄宽度
      const slideDistance = sliderBox.width - handleBox.width;
      const startX = handleBox.x + handleBox.width / 2;
      const startY = handleBox.y + handleBox.height / 2;
      const endX = startX + slideDistance;

      // 生成人类化的滑动轨迹
      const steps = 50; // 增加步数以模拟更平滑的移动
      const xTrajectory = this.generateHumanLikeMovement(startX, endX, steps);

      // 模拟鼠标移动
      await page.mouse.move(startX, startY);
      await page.mouse.down();

      for (const x of xTrajectory) {
        await page.mouse.move(x, startY);
        await scrapeSleep(5, 10); // 随机小延迟，模拟人类行为
      }

      await page.mouse.up();

      // 等待验证结果（可选）
      await scrapeSleep(1000, 2000);
      logger.info("滑动完成，等待验证结果...");
    } else {
      logger.error("无法获取滑块或手柄的边界框");
    }

  }
  // 生成模拟人类滑动的轨迹
  private generateHumanLikeMovement(start: number, end: number, steps: number): number[] {
    const trajectory = [];
    let current = start;
    const totalDistance = end - start;

    for (let i = 0; i < steps; i++) {
      // 模拟非线性移动：先快后慢
      const progress = i / steps;
      const easeOut = 1 - Math.pow(1 - progress, 2); // 缓动函数
      current = start + totalDistance * easeOut;
      trajectory.push(Math.round(current));
    }
    return trajectory;
  }
}
